# --------------------------------------------------------------------------------------------------------------------
# Project:             Digital Payment System (DPS)
# Website:             http://code.google.com/p/dps-x509/
# Purpose of document: This class validates and holds python representation 
#                      of x509 attributes
# --------------------------------------------------------------------------------------------------------------------
import sys, string, logging

sys.path.append("..")

#we would prefer the try to pass, but the import stuff isn't working
try:
  import keyczar.keys as keys
except ImportError:
  from keyczar import *
  
import dps.x509Asn1Extractor
import dps.errors
import dps.utils

# --------------------------------------------------------------------------------------------------------------------
class X509Certificate:
  """ Class that provides facade to keyzcar.utils functionality. """

# --------------------------------------------------------------------------------------------------------------------
  def __init__(self, extractor, privateKey= None):
    """ constructor 
    @param extractor: that interfaces with asn1
    @type  extractor: dps.x509Extractor
    @param privateKey: if None this will be generated for you
    @type  privateKey: keyczar.keys.RSAPrivateKey    
    """
    dps.utils.assertion(not privateKey or isinstance(privateKey, keys.RSAPublicKey), \
                        "X509Certificate.__init__: Invalid privateKey")
    dps.utils.assertion(isinstance(extractor, dps.x509Asn1Extractor.X509Asn1Extractor), \
                        "X509Certificate.__init__: Invalid extractor")
    
    self.__extractor_ = extractor
    self.__privateKey_ = privateKey
    self.__issuerCertificate_ = None
    
# --------------------------------------------------------------------------------------------------------------------
  def eq(self, other):
    """ 
    equals operator. compares base 64 output 
    @rtype: bool
    """
    return self.asBase64() == other.asBase64()

# --------------------------------------------------------------------------------------------------------------------
  def ne(self, other):
    """ 
    not equals operator. compares base 64 output 
    @rtype: bool
    """
    return not self == other  
  
# --------------------------------------------------------------------------------------------------------------------
  @staticmethod  
  def load(certificate, privateKey= None):
    """ load a certificate from string file contents 
    @param  certificate: certificate to load in der or base64 string formats
    @type   certificate: string
    @param  privateKey: if None this will be generated for you
    @type   privateKey: keyczar.keys.RSAPrivateKey    
    @raise  dps.errors.MissingRequiredParametersError: if required fields are missing. ie. token value, token type
      See http://code.google.com/p/dps-x509/wiki/ValueTokenDefinitions for more information
    @raise  dps.errors.NoRSADataFoundError: if dsa or other pki algorithm is used. RSA is currently the only supported.
    @raise  dps.errors.InvalidSignatureError: if the signature does not match the certificate
    @raise  dps.errors.DecodingError: if otherwise unable to create asn1 model from cert
    
    @rtype: X509Certificate
    """
    extractor = None
    dps.utils.assertion(not privateKey or isinstance(privateKey, keys.RsaPrivateKey), \
                        "X509Certificate.load: Invalid privateKey")
    try:
      extractor = dps.x509Asn1Extractor.X509Asn1Extractor(certificate)
          
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Certificate.load: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.DecodingError()
    
    dps.utils.assertion(extractor, "X509Certificate.load: extractor not created")
    x509 = X509Certificate(extractor, privateKey)
    return x509

  # --------------------------------------------------------------------------------------------------------------------
  def getParams(self):
    """ 
    get the properties out of this certificate
    @rtype: dps.x509CertificateParams.X509CertificateParams
    """
    return self.__extractor_.getParams()
      
  # --------------------------------------------------------------------------------------------------------------------
  def verifySignature(self, rsaPubKey):
    """ 
    check if the public key can be used to verify the certificate
    @type    rsaPubKey: keyczar.keys.RsaPublicKey 
    @return: true if the certificate is verifed by the public key
    @rtype:  bool
    """
    dps.utils.assertion(isinstance(rsaPubKey, keys.RsaPublicKey), \
                        "X509Certificate.verifySignature: Invalid rsaPubKey")
    
    isOk = False
    try:
      isOk = rsaPubKey.Verify(self.__extractor_.getTbsCertificateBytes(), self.__extractor_.getSignatureBytes())
    except Exception, e:
      print sys.exc_info()
      dps.utils.out("Exception in X509Certificate.verifySignature: " + str(sys.exc_info()[0]) + str(e))
    dps.utils.out("Result of X509Certificate.verifySignature: " + str(isOk))
    
    return isOk

  # --------------------------------------------------------------------------------------------------------------------
  def autoVerifySignature(self):
    """ check if this certificate was tampered with. 
    if issuer certificate is null the certificate is assumed to be self signed so this certificates public key 
    is used for verification.
    otherwise the issuer certificates public key is used
    @return: true if the certificate is verifed by the public key 
    @rtype:  bool
    """
    publicKey = self.getIssuerPublicKey() 
    return self.verifySignature(publicKey)  
  
  # --------------------------------------------------------------------------------------------------------------------
  def asBase64(self):
    """ 
    base64 representation without header and footer
    @rtype: string
    """
    return self.__extractor_.asBase64()
  
  # --------------------------------------------------------------------------------------------------------------------
  def prettyPrint(self):
    """ 
    returns string from asn1 pretty print 
    @rtype: string
    """
    return self.__extractor_.prettyPrint()
  
  # --------------------------------------------------------------------------------------------------------------------
  def isSelfSigned(self):
    """ 
    if it has an issuer and subject are the same it is not self signed 
    @rtype: bool 
    """
    return self.__extractor_.isSelfSigned()
  
  # --------------------------------------------------------------------------------------------------------------------
  def privateKey(self):
    """ 
    @rtype: keyczar.keys.RSAPrivateKey. this may be none 
    """
    return self.__privateKey_

  # --------------------------------------------------------------------------------------------------------------------
  def setIssuerCertificate(self, issuerCertificate):
    """ 
    sets the certificate that was used to sign this one
    @param issuerCertificate: issuer certificate
    @type  issuerCertificate: dps.x509Certificate.X509Certificate
    """
    dps.utils.assertion(not issuerCertificate or isinstance(issuerCertificate, dps.x509Certificate.X509Certificate), \
                        "X509Certificate.verifySignature: Invalid issuerCertificate")
    self.__issuerCertificate_ = issuerCertificate

  # --------------------------------------------------------------------------------------------------------------------
  def getIssuerPublicKey(self):
    """ 
    gets the public key used to issue this cert or None if it can't be found
    @rtype: keyczar.keys.RsaPublicKey
    """
    publicKey = None
    if self.isSelfSigned():
      publicKey = self.getParams().getPublicKey()
    elif self.__issuerCertificate_:
      publicKey = self.__issuerCertificate_.getParams().getPublicKey()
    
    return publicKey

  # --------------------------------------------------------------------------------------------------------------------
  def getSubjectAsBase64(self):
    """  """    """ 
    returns subject info
    @rtype: string
    """    
    return self.__extractor_.getSubjectBase64()

  # --------------------------------------------------------------------------------------------------------------------
  def getIssuerAsBase64(self):
    """ 
    returns issuer info
    @rtype: string
    """
    return self.__extractor_.getIssuerBase64()
  
# --------------------------------------------------------------------------------------------------------------------
if __name__ == "__main__":  
  try:
    base64 = """-----BEGIN CERTIFICATE-----
MIICoDCCAgmgAwIBAgIFAJH+SqIwDQYJKoZIhvcNAQEFBQAwUDELMAkGA1UEBhMC
QVUxDDAKBgNVBAgTA0FDVDERMA8GA1UEBxMIQ2FuYmVycmExIDAeBgNVBAMMF3Rp
bW9fbWF0aGFzQGhvdG1haWwuY29tMB4XDTEwMDkxMTAyMDc1OVoXDTEzMDkxMTAy
MDc1OVowUDELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA0FDVDERMA8GA1UEBxMIQ2Fu
YmVycmExIDAeBgNVBAMMF3RpbW9fbWF0aGFzQGhvdG1haWwuY29tMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC99Jed+KPnROHY5NPJZidyZ8N9ItHM/w4kd0hB
Lobbk2vlvpv4Cy2TCeRXZH6VHGudnsqpuxNvjaimNC/aHKZc1EL5Ej/BZcRoWVom
aaJB6f1UFwZZcq8FoUop8Kr03RYLoZqBJagqW47kgF3RHTgykTaYOZmdoRUY45go
IqVyKwIDAQABo4GFMIGCMBQGCWCGSAGG+EIBAQEB/wQEAwIEsDAgBgNVHQ4BAf8E
FgQUunY/thpP15uZmXhCSv3qVlthxUwwKgYDVR0lAQH/BCAwHgYIKwYBBQUHAwIG
CCsGAQUFBwMDBggrBgEFBQcDBDAOBgNVHQ8BAf8EBAMCA7gwDAYDVR0TAQH/BAIw
ADANBgkqhkiG9w0BAQUFAAOBgQAjBGZs3HcTn8PrODsf4mE4nwdh56zZYHuvQfHz
2aPk607esIiFQjwUpRuwqz1ToY+yTmDCAl17+9xgv1Ky+Qb+gZDt2KXCcEInE4oI
KQCADyjmbbVXFl2RK2YzVOfYGBFSMQzKcdwt+KnwiilqGimnBo5p7L96m4nb5rwD
7Z9GdQ==
-----END CERTIFICATE-----"""

    cert = X509Certificate.load(base64)
    cert.verifySignature(cert.getParams().getPublicKey())
    cert.autoVerifySignature()
    
  except Exception, e:
    dps.utils.out("An exception occured: " + str(sys.exc_info()[0]) + " " + str(e))
  